{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第4阶段_第5讲_线性回归分析\n",
    "\n",
    "## 学习目标\n",
    "1. 理解线性回归的基本原理和应用场景\n",
    "2. 掌握一元线性回归和多元线性回归\n",
    "3. 学会使用sklearn和statsmodels进行回归建模\n",
    "4. 理解回归模型的评估指标(R²、MSE、RMSE、MAE)\n",
    "5. 掌握回归诊断方法(残差分析、多重共线性检验)\n",
    "6. 能够进行业务场景的预测分析(销售预测、价格预测)\n",
    "\n",
    "## 什么是线性回归?\n",
    "\n",
    "**线性回归(Linear Regression)**是最基础和最常用的预测建模方法,用于:\n",
    "\n",
    "### 核心概念\n",
    "- 📊 **建立关系**: 找出自变量(X)和因变量(Y)之间的线性关系\n",
    "- 🔮 **预测未来**: 根据历史数据预测未来趋势\n",
    "- 📈 **量化影响**: 衡量各因素对结果的影响程度\n",
    "\n",
    "### 线性回归公式\n",
    "\n",
    "**一元线性回归**:\n",
    "$$Y = \\beta_0 + \\beta_1 X + \\epsilon$$\n",
    "\n",
    "**多元线性回归**:\n",
    "$$Y = \\beta_0 + \\beta_1 X_1 + \\beta_2 X_2 + ... + \\beta_n X_n + \\epsilon$$\n",
    "\n",
    "其中:\n",
    "- $Y$: 因变量(要预测的目标)\n",
    "- $X$: 自变量(影响因素)\n",
    "- $\\beta_0$: 截距(常数项)\n",
    "- $\\beta_1, \\beta_2, ..., \\beta_n$: 回归系数(斜率)\n",
    "- $\\epsilon$: 误差项\n",
    "\n",
    "### 应用场景\n",
    "- 📦 **销售预测**: 根据广告投入、季节等预测销售额\n",
    "- 🏠 **房价预测**: 根据面积、地段、楼层等预测房价\n",
    "- 💰 **薪资预测**: 根据工作年限、学历、技能等预测薪资\n",
    "- 📈 **业绩分析**: 分析各因素对业绩的影响程度\n",
    "\n",
    "## Excel vs Python回归分析\n",
    "\n",
    "| 功能 | Excel | Python (sklearn/statsmodels) |\n",
    "|------|-------|------------------------------|\n",
    "| 简单回归 | 数据分析-回归 | LinearRegression() |\n",
    "| 多元回归 | 数据分析-回归 | LinearRegression() |\n",
    "| 拟合优度 | R²输出 | model.score(), r2_score() |\n",
    "| 系数显示 | 自动输出 | model.coef_, model.intercept_ |\n",
    "| 残差分析 | 图表工具 | plt.plot(residuals) |\n",
    "| 显著性检验 | p值输出 | statsmodels OLS |\n",
    "| 预测 | 手动计算 | model.predict() |\n",
    "| 批量处理 | 手动重复 | 脚本自动化 |\n",
    "| 数据量 | <10万行 | 百万级+ |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入必要的库\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "# 机器学习库\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "# 统计建模库\n",
    "import statsmodels.api as sm\n",
    "from statsmodels.stats.outliers_influence import variance_inflation_factor\n",
    "from scipy import stats\n",
    "\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "# 设置中文显示\n",
    "plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "# 设置seaborn样式\n",
    "sns.set_style('whitegrid')\n",
    "\n",
    "# 设置显示选项\n",
    "pd.set_option('display.max_columns', None)\n",
    "pd.set_option('display.width', 1000)\n",
    "pd.set_option('display.float_format', '{:.4f}'.format)\n",
    "\n",
    "print(\"✅ 环境配置完成!\")\n",
    "print(f\"Pandas版本: {pd.__version__}\")\n",
    "print(f\"Scikit-learn版本: {import sklearn; sklearn.__version__}\" if 'sklearn' in dir() else \"Scikit-learn已导入\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一、一元线性回归(Simple Linear Regression)\n",
    "\n",
    "一元线性回归研究**一个自变量对因变量的影响**。\n",
    "\n",
    "### 案例:广告投入与销售额的关系\n",
    "\n",
    "某公司想研究广告投入与销售额的关系,收集了50周的数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成模拟数据:广告投入 vs 销售额\n",
    "np.random.seed(42)\n",
    "\n",
    "n_samples = 50\n",
    "\n",
    "# 广告投入(万元)\n",
    "ad_spend = np.random.uniform(5, 50, n_samples)\n",
    "\n",
    "# 销售额(万元) = 20 + 3 * 广告投入 + 噪声\n",
    "sales = 20 + 3 * ad_spend + np.random.normal(0, 10, n_samples)\n",
    "\n",
    "# 创建DataFrame\n",
    "simple_data = pd.DataFrame({\n",
    "    '周次': range(1, n_samples + 1),\n",
    "    '广告投入': ad_spend.round(2),\n",
    "    '销售额': sales.round(2)\n",
    "})\n",
    "\n",
    "print(\"广告投入与销售额数据:\")\n",
    "print(simple_data.head(10))\n",
    "print(f\"\\n数据集规模: {simple_data.shape[0]}行 × {simple_data.shape[1]}列\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第一步:数据探索\n",
    "print(\"=\"*100)\n",
    "print(\"📊 数据探索\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "print(\"\\n【基础统计】\")\n",
    "print(simple_data[['广告投入', '销售额']].describe())\n",
    "\n",
    "# 计算相关系数\n",
    "corr = simple_data['广告投入'].corr(simple_data['销售额'])\n",
    "print(f\"\\n【相关性分析】\")\n",
    "print(f\"广告投入与销售额的相关系数: {corr:.4f}\")\n",
    "if corr > 0.7:\n",
    "    print(\"结论: 强正相关,适合建立线性回归模型\")\n",
    "elif corr > 0.3:\n",
    "    print(\"结论: 中等正相关,可以尝试建立线性回归模型\")\n",
    "else:\n",
    "    print(\"结论: 弱相关,线性回归效果可能不佳\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 可视化:散点图\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.scatter(simple_data['广告投入'], simple_data['销售额'], alpha=0.6, s=80, color='steelblue', edgecolors='black')\n",
    "plt.xlabel('广告投入(万元)', fontsize=12)\n",
    "plt.ylabel('销售额(万元)', fontsize=12)\n",
    "plt.title(f'广告投入 vs 销售额散点图 (相关系数={corr:.3f})', fontsize=14, fontweight='bold')\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\n📊 图表说明:\")\n",
    "print(\"  散点整体呈现从左下到右上的趋势,说明存在正相关关系\")\n",
    "print(\"  适合使用线性回归建模\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第二步:建立线性回归模型\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"🔨 建立一元线性回归模型\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 准备数据\n",
    "X = simple_data[['广告投入']].values  # 自变量(需要是2D数组)\n",
    "y = simple_data['销售额'].values      # 因变量\n",
    "\n",
    "# 创建线性回归模型\n",
    "model = LinearRegression()\n",
    "\n",
    "# 训练模型\n",
    "model.fit(X, y)\n",
    "\n",
    "# 获取模型参数\n",
    "intercept = model.intercept_  # 截距 β0\n",
    "coef = model.coef_[0]         # 斜率 β1\n",
    "\n",
    "print(\"\\n【模型参数】\")\n",
    "print(f\"截距(β0):     {intercept:.4f}\")\n",
    "print(f\"斜率(β1):     {coef:.4f}\")\n",
    "print(f\"\\n回归方程:\")\n",
    "print(f\"销售额 = {intercept:.2f} + {coef:.2f} × 广告投入\")\n",
    "\n",
    "print(\"\\n【模型解读】\")\n",
    "print(f\"  - 截距{intercept:.2f}: 当广告投入为0时,预期销售额为{intercept:.2f}万元(基础销售额)\")\n",
    "print(f\"  - 斜率{coef:.2f}: 广告投入每增加1万元,销售额预期增加{coef:.2f}万元\")\n",
    "print(f\"  - ROI(投资回报率): {coef:.2f}:1,即每投入1元广告,可带来{coef:.2f}元销售额\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第三步:模型评估\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"📈 模型评估\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 预测\n",
    "y_pred = model.predict(X)\n",
    "\n",
    "# 计算评估指标\n",
    "r2 = r2_score(y, y_pred)\n",
    "mse = mean_squared_error(y, y_pred)\n",
    "rmse = np.sqrt(mse)\n",
    "mae = mean_absolute_error(y, y_pred)\n",
    "\n",
    "print(\"\\n【评估指标】\")\n",
    "print(f\"R²(决定系数):          {r2:.4f}\")\n",
    "print(f\"MSE(均方误差):         {mse:.4f}\")\n",
    "print(f\"RMSE(均方根误差):      {rmse:.4f} 万元\")\n",
    "print(f\"MAE(平均绝对误差):     {mae:.4f} 万元\")\n",
    "\n",
    "print(\"\\n【指标解读】\")\n",
    "print(f\"  R² = {r2:.4f}:\")\n",
    "if r2 >= 0.9:\n",
    "    print(f\"    模型拟合优秀,{r2*100:.2f}%的销售额变化可由广告投入解释\")\n",
    "elif r2 >= 0.7:\n",
    "    print(f\"    模型拟合良好,{r2*100:.2f}%的销售额变化可由广告投入解释\")\n",
    "elif r2 >= 0.5:\n",
    "    print(f\"    模型拟合一般,{r2*100:.2f}%的销售额变化可由广告投入解释\")\n",
    "else:\n",
    "    print(f\"    模型拟合较差,仅{r2*100:.2f}%的销售额变化可由广告投入解释\")\n",
    "\n",
    "print(f\"\\n  RMSE = {rmse:.2f} 万元:\")\n",
    "print(f\"    模型预测的平均误差约为{rmse:.2f}万元\")\n",
    "\n",
    "print(f\"\\n  MAE = {mae:.2f} 万元:\")\n",
    "print(f\"    预测值与实际值的平均偏差为{mae:.2f}万元\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 可视化:回归线\n",
    "fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n",
    "\n",
    "# 左图:散点图+回归线\n",
    "axes[0].scatter(X, y, alpha=0.6, s=80, color='steelblue', edgecolors='black', label='实际值')\n",
    "axes[0].plot(X, y_pred, color='red', linewidth=2, label=f'回归线: y={intercept:.2f}+{coef:.2f}x')\n",
    "axes[0].set_xlabel('广告投入(万元)', fontsize=12)\n",
    "axes[0].set_ylabel('销售额(万元)', fontsize=12)\n",
    "axes[0].set_title(f'一元线性回归拟合结果 (R²={r2:.4f})', fontsize=13, fontweight='bold')\n",
    "axes[0].legend(fontsize=11)\n",
    "axes[0].grid(alpha=0.3)\n",
    "\n",
    "# 右图:实际值 vs 预测值\n",
    "axes[1].scatter(y, y_pred, alpha=0.6, s=80, color='green', edgecolors='black')\n",
    "# 添加理想预测线(y=x)\n",
    "min_val = min(y.min(), y_pred.min())\n",
    "max_val = max(y.max(), y_pred.max())\n",
    "axes[1].plot([min_val, max_val], [min_val, max_val], 'r--', linewidth=2, label='理想预测线(y=x)')\n",
    "axes[1].set_xlabel('实际销售额(万元)', fontsize=12)\n",
    "axes[1].set_ylabel('预测销售额(万元)', fontsize=12)\n",
    "axes[1].set_title('实际值 vs 预测值', fontsize=13, fontweight='bold')\n",
    "axes[1].legend(fontsize=11)\n",
    "axes[1].grid(alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\n📊 图表说明:\")\n",
    "print(\"  左图: 红色回归线穿过散点,拟合数据趋势\")\n",
    "print(\"  右图: 点越接近红色虚线(y=x),说明预测越准确\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第四步:残差分析\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"🔍 残差分析\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 计算残差\n",
    "residuals = y - y_pred\n",
    "\n",
    "print(\"\\n【残差统计】\")\n",
    "print(f\"残差均值:     {residuals.mean():.4f} (应接近0)\")\n",
    "print(f\"残差标准差:   {residuals.std():.4f}\")\n",
    "print(f\"残差最小值:   {residuals.min():.4f}\")\n",
    "print(f\"残差最大值:   {residuals.max():.4f}\")\n",
    "\n",
    "# 正态性检验(Shapiro-Wilk检验)\n",
    "shapiro_stat, shapiro_p = stats.shapiro(residuals)\n",
    "print(f\"\\n【残差正态性检验】\")\n",
    "print(f\"Shapiro-Wilk统计量: {shapiro_stat:.4f}\")\n",
    "print(f\"p值:                {shapiro_p:.4f}\")\n",
    "if shapiro_p > 0.05:\n",
    "    print(\"✅ 残差服从正态分布(p>0.05),满足线性回归假设\")\n",
    "else:\n",
    "    print(\"⚠️ 残差不服从正态分布(p≤0.05),可能违反线性回归假设\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 残差可视化(四图诊断)\n",
    "fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n",
    "\n",
    "# 1. 残差 vs 拟合值(检验线性假设和同方差性)\n",
    "axes[0, 0].scatter(y_pred, residuals, alpha=0.6, s=60, color='blue', edgecolors='black')\n",
    "axes[0, 0].axhline(y=0, color='red', linestyle='--', linewidth=2)\n",
    "axes[0, 0].set_xlabel('拟合值', fontsize=11)\n",
    "axes[0, 0].set_ylabel('残差', fontsize=11)\n",
    "axes[0, 0].set_title('残差 vs 拟合值', fontsize=12, fontweight='bold')\n",
    "axes[0, 0].grid(alpha=0.3)\n",
    "axes[0, 0].text(0.05, 0.95, '理想情况:点随机分布在0线两侧', \n",
    "                transform=axes[0, 0].transAxes, fontsize=9, verticalalignment='top',\n",
    "                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))\n",
    "\n",
    "# 2. 残差直方图(检验正态性)\n",
    "axes[0, 1].hist(residuals, bins=15, color='green', alpha=0.7, edgecolor='black')\n",
    "axes[0, 1].axvline(x=0, color='red', linestyle='--', linewidth=2)\n",
    "axes[0, 1].set_xlabel('残差', fontsize=11)\n",
    "axes[0, 1].set_ylabel('频数', fontsize=11)\n",
    "axes[0, 1].set_title('残差分布直方图', fontsize=12, fontweight='bold')\n",
    "axes[0, 1].grid(axis='y', alpha=0.3)\n",
    "axes[0, 1].text(0.05, 0.95, '理想情况:呈正态分布', \n",
    "                transform=axes[0, 1].transAxes, fontsize=9, verticalalignment='top',\n",
    "                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))\n",
    "\n",
    "# 3. Q-Q图(检验正态性)\n",
    "stats.probplot(residuals, dist=\"norm\", plot=axes[1, 0])\n",
    "axes[1, 0].set_title('残差Q-Q图', fontsize=12, fontweight='bold')\n",
    "axes[1, 0].grid(alpha=0.3)\n",
    "axes[1, 0].text(0.05, 0.95, '理想情况:点接近直线', \n",
    "                transform=axes[1, 0].transAxes, fontsize=9, verticalalignment='top',\n",
    "                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))\n",
    "\n",
    "# 4. 残差序列图(检验独立性)\n",
    "axes[1, 1].plot(range(len(residuals)), residuals, marker='o', linestyle='-', color='purple', alpha=0.6)\n",
    "axes[1, 1].axhline(y=0, color='red', linestyle='--', linewidth=2)\n",
    "axes[1, 1].set_xlabel('样本序号', fontsize=11)\n",
    "axes[1, 1].set_ylabel('残差', fontsize=11)\n",
    "axes[1, 1].set_title('残差序列图', fontsize=12, fontweight='bold')\n",
    "axes[1, 1].grid(alpha=0.3)\n",
    "axes[1, 1].text(0.05, 0.95, '理想情况:无明显趋势或周期', \n",
    "                transform=axes[1, 1].transAxes, fontsize=9, verticalalignment='top',\n",
    "                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\n📊 残差分析结论:\")\n",
    "print(\"  1. 残差vs拟合值: 点随机分布在0线两侧,满足线性假设和同方差性\")\n",
    "print(\"  2. 残差直方图: 大致呈正态分布\")\n",
    "print(\"  3. Q-Q图: 点基本在直线上,残差满足正态性\")\n",
    "print(\"  4. 残差序列图: 无明显趋势,残差相互独立\")\n",
    "print(\"  ✅ 模型满足线性回归的基本假设,可以使用!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第五步:预测\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"🔮 使用模型进行预测\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 预测不同广告投入下的销售额\n",
    "test_ad_spend = np.array([[10], [20], [30], [40], [50]])  # 10万、20万...50万\n",
    "predicted_sales = model.predict(test_ad_spend)\n",
    "\n",
    "print(\"\\n【预测结果】\")\n",
    "print(\"=\"*60)\n",
    "print(f\"{'广告投入(万元)':<20} {'预测销售额(万元)':<20} {'预期增量(万元)':<20}\")\n",
    "print(\"=\"*60)\n",
    "for i, (ad, sale) in enumerate(zip(test_ad_spend, predicted_sales)):\n",
    "    if i == 0:\n",
    "        increment = \"-\"\n",
    "    else:\n",
    "        increment = f\"{sale - predicted_sales[i-1]:.2f}\"\n",
    "    print(f\"{ad[0]:<20.1f} {sale:<20.2f} {increment:<20}\")\n",
    "print(\"=\"*60)\n",
    "\n",
    "print(\"\\n【业务建议】\")\n",
    "print(f\"  - 当前模型ROI为{coef:.2f}:1\")\n",
    "print(f\"  - 如果期望销售额达到150万元,需要广告投入: {(150-intercept)/coef:.2f}万元\")\n",
    "print(f\"  - 建议根据预算和目标销售额灵活调整广告投入\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二、多元线性回归(Multiple Linear Regression)\n",
    "\n",
    "多元线性回归研究**多个自变量对因变量的影响**。\n",
    "\n",
    "### 案例:房价预测\n",
    "\n",
    "某地区房价受多个因素影响:面积、房龄、楼层、距地铁距离等。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成模拟数据:房价预测\n",
    "np.random.seed(42)\n",
    "\n",
    "n_houses = 200\n",
    "\n",
    "# 生成特征\n",
    "area = np.random.uniform(50, 150, n_houses)           # 面积(平米)\n",
    "age = np.random.uniform(0, 30, n_houses)              # 房龄(年)\n",
    "floor = np.random.randint(1, 31, n_houses)            # 楼层\n",
    "metro_distance = np.random.uniform(0.1, 5, n_houses)  # 距地铁距离(公里)\n",
    "rooms = np.random.randint(1, 5, n_houses)             # 房间数\n",
    "\n",
    "# 生成房价(万元) = 100 + 0.5*面积 - 2*房龄 + 0.3*楼层 - 5*距地铁 + 10*房间数 + 噪声\n",
    "price = (100 + 0.5 * area - 2 * age + 0.3 * floor - 5 * metro_distance + 10 * rooms + \n",
    "         np.random.normal(0, 15, n_houses))\n",
    "\n",
    "# 创建DataFrame\n",
    "housing_data = pd.DataFrame({\n",
    "    '房屋编号': [f'H{str(i).zfill(4)}' for i in range(1, n_houses+1)],\n",
    "    '面积': area.round(2),\n",
    "    '房龄': age.round(1),\n",
    "    '楼层': floor,\n",
    "    '距地铁距离': metro_distance.round(2),\n",
    "    '房间数': rooms,\n",
    "    '房价': price.round(2)\n",
    "})\n",
    "\n",
    "print(\"房价数据集:\")\n",
    "print(housing_data.head(10))\n",
    "print(f\"\\n数据集规模: {housing_data.shape[0]}行 × {housing_data.shape[1]}列\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第一步:数据探索\n",
    "print(\"=\"*100)\n",
    "print(\"📊 房价数据探索\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "print(\"\\n【基础统计】\")\n",
    "print(housing_data.describe().T)\n",
    "\n",
    "# 相关性分析\n",
    "feature_cols = ['面积', '房龄', '楼层', '距地铁距离', '房间数', '房价']\n",
    "corr_matrix = housing_data[feature_cols].corr()\n",
    "\n",
    "print(\"\\n【相关系数矩阵】\")\n",
    "print(corr_matrix['房价'].sort_values(ascending=False).round(4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 可视化:相关系数热力图和散点图矩阵\n",
    "fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n",
    "\n",
    "# 左图:相关系数热力图\n",
    "sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm', center=0,\n",
    "            square=True, linewidths=1, cbar_kws={'label': '相关系数'},\n",
    "            vmin=-1, vmax=1, ax=axes[0])\n",
    "axes[0].set_title('变量相关系数热力图', fontsize=13, fontweight='bold')\n",
    "\n",
    "# 右图:房价与各特征的箱线图对比\n",
    "axes[1].axis('off')\n",
    "inner_grid = axes[1].inset_axes([0, 0, 1, 1]).get_gridspec()\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# 绘制房价与各特征的散点图\n",
    "fig, axes = plt.subplots(2, 3, figsize=(16, 10))\n",
    "axes = axes.flatten()\n",
    "\n",
    "for idx, col in enumerate(['面积', '房龄', '楼层', '距地铁距离', '房间数']):\n",
    "    axes[idx].scatter(housing_data[col], housing_data['房价'], alpha=0.5, s=30, color='steelblue')\n",
    "    axes[idx].set_xlabel(col, fontsize=11)\n",
    "    axes[idx].set_ylabel('房价(万元)', fontsize=11)\n",
    "    axes[idx].set_title(f'房价 vs {col}', fontsize=12, fontweight='bold')\n",
    "    axes[idx].grid(alpha=0.3)\n",
    "    \n",
    "    # 添加相关系数\n",
    "    corr_val = housing_data[col].corr(housing_data['房价'])\n",
    "    axes[idx].text(0.05, 0.95, f'r={corr_val:.3f}', transform=axes[idx].transAxes,\n",
    "                   fontsize=10, verticalalignment='top',\n",
    "                   bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))\n",
    "\n",
    "# 隐藏最后一个子图\n",
    "axes[5].axis('off')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\n📊 数据探索结论:\")\n",
    "print(\"  - 面积与房价呈强正相关(r>0.7)\")\n",
    "print(\"  - 房龄与房价呈负相关(房龄越大,房价越低)\")\n",
    "print(\"  - 距地铁距离与房价呈负相关(距离越远,房价越低)\")\n",
    "print(\"  - 房间数与房价呈正相关\")\n",
    "print(\"  - 适合建立多元线性回归模型\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第二步:数据划分(训练集和测试集)\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"✂️ 划分训练集和测试集\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 准备特征和目标变量\n",
    "X = housing_data[['面积', '房龄', '楼层', '距地铁距离', '房间数']].values\n",
    "y = housing_data['房价'].values\n",
    "\n",
    "# 划分训练集(80%)和测试集(20%)\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
    "\n",
    "print(f\"\\n训练集大小: {X_train.shape[0]}个样本\")\n",
    "print(f\"测试集大小: {X_test.shape[0]}个样本\")\n",
    "print(f\"特征数量:   {X_train.shape[1]}个\")\n",
    "\n",
    "print(\"\\n💡 说明:\")\n",
    "print(\"  - 训练集用于训练模型(学习参数)\")\n",
    "print(\"  - 测试集用于评估模型(模拟真实预测场景)\")\n",
    "print(\"  - 80/20划分是常用比例\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第三步:建立多元线性回归模型\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"🔨 建立多元线性回归模型\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 创建并训练模型\n",
    "mlr_model = LinearRegression()\n",
    "mlr_model.fit(X_train, y_train)\n",
    "\n",
    "# 获取模型参数\n",
    "intercept_mlr = mlr_model.intercept_\n",
    "coefs_mlr = mlr_model.coef_\n",
    "\n",
    "print(\"\\n【模型参数】\")\n",
    "print(f\"截距(β0):     {intercept_mlr:.4f}\")\n",
    "print(\"\\n各特征的回归系数:\")\n",
    "feature_names = ['面积', '房龄', '楼层', '距地铁距离', '房间数']\n",
    "for name, coef in zip(feature_names, coefs_mlr):\n",
    "    print(f\"  {name:<15}: β={coef:>8.4f}\")\n",
    "\n",
    "print(f\"\\n【回归方程】\")\n",
    "equation = f\"房价 = {intercept_mlr:.2f}\"\n",
    "for name, coef in zip(feature_names, coefs_mlr):\n",
    "    sign = '+' if coef >= 0 else ''\n",
    "    equation += f\" {sign}{coef:.2f}×{name}\"\n",
    "print(equation)\n",
    "\n",
    "print(\"\\n【系数解读】\")\n",
    "for name, coef in zip(feature_names, coefs_mlr):\n",
    "    if coef > 0:\n",
    "        print(f\"  {name}: 每增加1单位,房价预期增加{abs(coef):.2f}万元\")\n",
    "    else:\n",
    "        print(f\"  {name}: 每增加1单位,房价预期减少{abs(coef):.2f}万元\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第四步:模型评估\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"📈 模型评估\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 在训练集上预测\n",
    "y_train_pred = mlr_model.predict(X_train)\n",
    "# 在测试集上预测\n",
    "y_test_pred = mlr_model.predict(X_test)\n",
    "\n",
    "# 计算训练集指标\n",
    "r2_train = r2_score(y_train, y_train_pred)\n",
    "rmse_train = np.sqrt(mean_squared_error(y_train, y_train_pred))\n",
    "mae_train = mean_absolute_error(y_train, y_train_pred)\n",
    "\n",
    "# 计算测试集指标\n",
    "r2_test = r2_score(y_test, y_test_pred)\n",
    "rmse_test = np.sqrt(mean_squared_error(y_test, y_test_pred))\n",
    "mae_test = mean_absolute_error(y_test, y_test_pred)\n",
    "\n",
    "print(\"\\n【评估指标对比】\")\n",
    "print(\"=\"*80)\n",
    "print(f\"{'指标':<20} {'训练集':<25} {'测试集':<25}\")\n",
    "print(\"=\"*80)\n",
    "print(f\"{'R²':<20} {r2_train:<25.4f} {r2_test:<25.4f}\")\n",
    "print(f\"{'RMSE(万元)':<20} {rmse_train:<25.4f} {rmse_test:<25.4f}\")\n",
    "print(f\"{'MAE(万元)':<20} {mae_train:<25.4f} {mae_test:<25.4f}\")\n",
    "print(\"=\"*80)\n",
    "\n",
    "print(\"\\n【模型性能分析】\")\n",
    "print(f\"  训练集R² = {r2_train:.4f}: 模型在训练数据上能解释{r2_train*100:.2f}%的房价变化\")\n",
    "print(f\"  测试集R² = {r2_test:.4f}: 模型在新数据上能解释{r2_test*100:.2f}%的房价变化\")\n",
    "\n",
    "# 判断过拟合\n",
    "r2_diff = r2_train - r2_test\n",
    "if r2_diff < 0.05:\n",
    "    print(f\"\\n  ✅ 训练集和测试集R²差异小于0.05,模型泛化能力良好\")\n",
    "elif r2_diff < 0.1:\n",
    "    print(f\"\\n  ⚠️ 训练集和测试集R²差异为{r2_diff:.4f},存在轻微过拟合\")\n",
    "else:\n",
    "    print(f\"\\n  ❌ 训练集和测试集R²差异为{r2_diff:.4f},存在明显过拟合\")\n",
    "\n",
    "print(f\"\\n  测试集RMSE = {rmse_test:.2f}万元: 模型预测误差平均约{rmse_test:.2f}万元\")\n",
    "print(f\"  测试集MAE = {mae_test:.2f}万元: 预测值平均偏差约{mae_test:.2f}万元\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 可视化:预测效果\n",
    "fig, axes = plt.subplots(2, 2, figsize=(14, 12))\n",
    "\n",
    "# 1. 训练集:实际vs预测\n",
    "axes[0, 0].scatter(y_train, y_train_pred, alpha=0.5, s=40, color='blue', edgecolors='black')\n",
    "min_val = min(y_train.min(), y_train_pred.min())\n",
    "max_val = max(y_train.max(), y_train_pred.max())\n",
    "axes[0, 0].plot([min_val, max_val], [min_val, max_val], 'r--', linewidth=2, label='理想预测线')\n",
    "axes[0, 0].set_xlabel('实际房价(万元)', fontsize=11)\n",
    "axes[0, 0].set_ylabel('预测房价(万元)', fontsize=11)\n",
    "axes[0, 0].set_title(f'训练集: 实际 vs 预测 (R²={r2_train:.4f})', fontsize=12, fontweight='bold')\n",
    "axes[0, 0].legend(fontsize=10)\n",
    "axes[0, 0].grid(alpha=0.3)\n",
    "\n",
    "# 2. 测试集:实际vs预测\n",
    "axes[0, 1].scatter(y_test, y_test_pred, alpha=0.5, s=40, color='green', edgecolors='black')\n",
    "min_val = min(y_test.min(), y_test_pred.min())\n",
    "max_val = max(y_test.max(), y_test_pred.max())\n",
    "axes[0, 1].plot([min_val, max_val], [min_val, max_val], 'r--', linewidth=2, label='理想预测线')\n",
    "axes[0, 1].set_xlabel('实际房价(万元)', fontsize=11)\n",
    "axes[0, 1].set_ylabel('预测房价(万元)', fontsize=11)\n",
    "axes[0, 1].set_title(f'测试集: 实际 vs 预测 (R²={r2_test:.4f})', fontsize=12, fontweight='bold')\n",
    "axes[0, 1].legend(fontsize=10)\n",
    "axes[0, 1].grid(alpha=0.3)\n",
    "\n",
    "# 3. 残差分布(测试集)\n",
    "residuals_test = y_test - y_test_pred\n",
    "axes[1, 0].hist(residuals_test, bins=20, color='coral', alpha=0.7, edgecolor='black')\n",
    "axes[1, 0].axvline(x=0, color='red', linestyle='--', linewidth=2)\n",
    "axes[1, 0].set_xlabel('残差(万元)', fontsize=11)\n",
    "axes[1, 0].set_ylabel('频数', fontsize=11)\n",
    "axes[1, 0].set_title('测试集残差分布', fontsize=12, fontweight='bold')\n",
    "axes[1, 0].grid(axis='y', alpha=0.3)\n",
    "\n",
    "# 4. 特征重要性(系数绝对值)\n",
    "importance = pd.DataFrame({\n",
    "    '特征': feature_names,\n",
    "    '系数': coefs_mlr,\n",
    "    '绝对值': np.abs(coefs_mlr)\n",
    "}).sort_values('绝对值', ascending=True)\n",
    "\n",
    "colors = ['red' if x < 0 else 'green' for x in importance['系数']]\n",
    "axes[1, 1].barh(importance['特征'], importance['系数'], color=colors, alpha=0.7, edgecolor='black')\n",
    "axes[1, 1].axvline(x=0, color='black', linestyle='-', linewidth=1)\n",
    "axes[1, 1].set_xlabel('回归系数', fontsize=11)\n",
    "axes[1, 1].set_title('各特征的回归系数', fontsize=12, fontweight='bold')\n",
    "axes[1, 1].grid(axis='x', alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\n📊 可视化说明:\")\n",
    "print(\"  左上: 训练集预测效果,点越接近红线越好\")\n",
    "print(\"  右上: 测试集预测效果,评估模型泛化能力\")\n",
    "print(\"  左下: 残差分布接近正态,说明模型假设成立\")\n",
    "print(\"  右下: 各特征的影响方向和强度,绿色=正向,红色=负向\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第五步:使用statsmodels进行详细统计分析\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"📊 使用statsmodels进行详细统计检验\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 添加常数项(截距)\n",
    "X_train_sm = sm.add_constant(X_train)\n",
    "X_test_sm = sm.add_constant(X_test)\n",
    "\n",
    "# 使用OLS(普通最小二乘法)建模\n",
    "ols_model = sm.OLS(y_train, X_train_sm)\n",
    "results = ols_model.fit()\n",
    "\n",
    "# 输出详细统计报告\n",
    "print(\"\\n【回归统计报告】\")\n",
    "print(results.summary())\n",
    "\n",
    "print(\"\\n\\n💡 关键指标解读:\")\n",
    "print(f\"  R-squared:        {results.rsquared:.4f} (拟合优度)\")\n",
    "print(f\"  Adj. R-squared:   {results.rsquared_adj:.4f} (调整后R²,考虑变量数量)\")\n",
    "print(f\"  F-statistic:      {results.fvalue:.4f} (整体显著性检验)\")\n",
    "print(f\"  Prob (F):         {results.f_pvalue:.6f} (p<0.05说明模型整体显著)\")\n",
    "\n",
    "print(\"\\n【各系数的显著性检验】\")\n",
    "print(\"=\"*80)\n",
    "print(f\"{'变量':<15} {'系数':<12} {'标准误':<12} {'t值':<12} {'p值':<12} {'显著性':<12}\")\n",
    "print(\"=\"*80)\n",
    "coef_names = ['截距'] + feature_names\n",
    "for i, name in enumerate(coef_names):\n",
    "    coef = results.params[i]\n",
    "    std_err = results.bse[i]\n",
    "    t_val = results.tvalues[i]\n",
    "    p_val = results.pvalues[i]\n",
    "    sig = '***' if p_val < 0.001 else '**' if p_val < 0.01 else '*' if p_val < 0.05 else ''\n",
    "    print(f\"{name:<15} {coef:<12.4f} {std_err:<12.4f} {t_val:<12.4f} {p_val:<12.6f} {sig:<12}\")\n",
    "print(\"=\"*80)\n",
    "print(\"显著性标记: *** p<0.001, ** p<0.01, * p<0.05\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第六步:多重共线性检验(VIF)\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"🔍 多重共线性检验(VIF)\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 计算VIF(方差膨胀因子)\n",
    "vif_data = pd.DataFrame()\n",
    "vif_data['特征'] = feature_names\n",
    "vif_data['VIF'] = [variance_inflation_factor(X_train, i) for i in range(X_train.shape[1])]\n",
    "vif_data = vif_data.sort_values('VIF', ascending=False)\n",
    "\n",
    "print(\"\\n【VIF检验结果】\")\n",
    "print(\"=\"*50)\n",
    "print(vif_data.to_string(index=False))\n",
    "print(\"=\"*50)\n",
    "\n",
    "print(\"\\n💡 VIF判断标准:\")\n",
    "print(\"  VIF < 5:    无多重共线性问题\")\n",
    "print(\"  5 ≤ VIF < 10: 存在中等程度多重共线性\")\n",
    "print(\"  VIF ≥ 10:   存在严重多重共线性,需处理\")\n",
    "\n",
    "max_vif = vif_data['VIF'].max()\n",
    "if max_vif < 5:\n",
    "    print(f\"\\n✅ 最大VIF={max_vif:.2f},无多重共线性问题\")\n",
    "elif max_vif < 10:\n",
    "    print(f\"\\n⚠️ 最大VIF={max_vif:.2f},存在中等多重共线性\")\n",
    "else:\n",
    "    print(f\"\\n❌ 最大VIF={max_vif:.2f},存在严重多重共线性,建议删除相关变量\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 第七步:预测新房价格\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"🔮 预测新房价格\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 创建几个待预测的房屋\n",
    "new_houses = pd.DataFrame({\n",
    "    '面积': [80, 100, 120, 90, 110],\n",
    "    '房龄': [5, 10, 2, 15, 8],\n",
    "    '楼层': [15, 20, 10, 5, 18],\n",
    "    '距地铁距离': [0.5, 1.0, 0.3, 2.0, 0.8],\n",
    "    '房间数': [2, 3, 3, 2, 3]\n",
    "})\n",
    "\n",
    "# 预测\n",
    "predicted_prices = mlr_model.predict(new_houses[feature_names].values)\n",
    "new_houses['预测房价'] = predicted_prices.round(2)\n",
    "\n",
    "print(\"\\n【预测结果】\")\n",
    "print(\"=\"*100)\n",
    "print(new_houses.to_string(index=False))\n",
    "print(\"=\"*100)\n",
    "\n",
    "print(\"\\n【预测示例解读】\")\n",
    "print(f\"  房屋1: 80平米,5年房龄,15楼,距地铁500米,2室 → 预测房价{predicted_prices[0]:.2f}万元\")\n",
    "print(f\"  房屋2: 100平米,10年房龄,20楼,距地铁1公里,3室 → 预测房价{predicted_prices[1]:.2f}万元\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 三、线性回归的假设和诊断\n",
    "\n",
    "线性回归模型基于以下假设:\n",
    "\n",
    "### 3.1 线性回归的五大假设\n",
    "\n",
    "1. **线性关系**: 自变量和因变量之间存在线性关系\n",
    "2. **独立性**: 观测值之间相互独立\n",
    "3. **同方差性**: 残差的方差恒定(不随X变化)\n",
    "4. **正态性**: 残差服从正态分布\n",
    "5. **无多重共线性**: 自变量之间不存在严重相关\n",
    "\n",
    "### 3.2 模型诊断方法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 完整的模型诊断\n",
    "print(\"=\"*100)\n",
    "print(\"🔬 线性回归模型完整诊断\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 计算残差\n",
    "residuals = y_test - y_test_pred\n",
    "standardized_residuals = (residuals - residuals.mean()) / residuals.std()\n",
    "\n",
    "print(\"\\n【1. 线性关系检验】\")\n",
    "print(\"  方法: 观察残差vs拟合值图\")\n",
    "print(\"  期望: 残差随机分布在0线两侧,无明显模式\")\n",
    "\n",
    "print(\"\\n【2. 独立性检验】\")\n",
    "print(\"  方法: Durbin-Watson检验\")\n",
    "from statsmodels.stats.stattools import durbin_watson\n",
    "dw_stat = durbin_watson(residuals)\n",
    "print(f\"  DW统计量: {dw_stat:.4f}\")\n",
    "print(f\"  判断: \", end=\"\")\n",
    "if 1.5 <= dw_stat <= 2.5:\n",
    "    print(\"✅ 残差独立(DW接近2)\")\n",
    "elif dw_stat < 1.5:\n",
    "    print(\"⚠️ 存在正自相关\")\n",
    "else:\n",
    "    print(\"⚠️ 存在负自相关\")\n",
    "\n",
    "print(\"\\n【3. 同方差性检验】\")\n",
    "print(\"  方法: Breusch-Pagan检验\")\n",
    "from statsmodels.stats.diagnostic import het_breuschpagan\n",
    "bp_test = het_breuschpagan(residuals, X_test)\n",
    "print(f\"  BP统计量: {bp_test[0]:.4f}\")\n",
    "print(f\"  p值:      {bp_test[1]:.4f}\")\n",
    "if bp_test[1] > 0.05:\n",
    "    print(\"  ✅ 满足同方差性(p>0.05)\")\n",
    "else:\n",
    "    print(\"  ⚠️ 存在异方差性(p≤0.05),可能需要变换\")\n",
    "\n",
    "print(\"\\n【4. 正态性检验】\")\n",
    "shapiro_stat, shapiro_p = stats.shapiro(residuals)\n",
    "print(f\"  Shapiro-Wilk统计量: {shapiro_stat:.4f}\")\n",
    "print(f\"  p值:                {shapiro_p:.4f}\")\n",
    "if shapiro_p > 0.05:\n",
    "    print(\"  ✅ 残差服从正态分布(p>0.05)\")\n",
    "else:\n",
    "    print(\"  ⚠️ 残差不服从正态分布(p≤0.05)\")\n",
    "\n",
    "print(\"\\n【5. 多重共线性检验】\")\n",
    "print(f\"  最大VIF: {max_vif:.2f}\")\n",
    "if max_vif < 5:\n",
    "    print(\"  ✅ 无多重共线性问题\")\n",
    "elif max_vif < 10:\n",
    "    print(\"  ⚠️ 存在中等多重共线性\")\n",
    "else:\n",
    "    print(\"  ❌ 存在严重多重共线性\")\n",
    "\n",
    "print(\"\\n\" + \"=\"*100)\n",
    "print(\"✅ 模型诊断完成!\")\n",
    "print(\"=\"*100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 完整的诊断图\n",
    "fig, axes = plt.subplots(2, 3, figsize=(18, 12))\n",
    "\n",
    "# 1. 残差vs拟合值(检验线性和同方差性)\n",
    "axes[0, 0].scatter(y_test_pred, residuals, alpha=0.5, s=40, color='blue')\n",
    "axes[0, 0].axhline(y=0, color='red', linestyle='--', linewidth=2)\n",
    "axes[0, 0].set_xlabel('拟合值', fontsize=11)\n",
    "axes[0, 0].set_ylabel('残差', fontsize=11)\n",
    "axes[0, 0].set_title('残差 vs 拟合值', fontsize=12, fontweight='bold')\n",
    "axes[0, 0].grid(alpha=0.3)\n",
    "\n",
    "# 2. 标准化残差vs拟合值\n",
    "axes[0, 1].scatter(y_test_pred, standardized_residuals, alpha=0.5, s=40, color='green')\n",
    "axes[0, 1].axhline(y=0, color='red', linestyle='--', linewidth=2)\n",
    "axes[0, 1].axhline(y=2, color='orange', linestyle=':', linewidth=1.5, label='±2σ')\n",
    "axes[0, 1].axhline(y=-2, color='orange', linestyle=':', linewidth=1.5)\n",
    "axes[0, 1].set_xlabel('拟合值', fontsize=11)\n",
    "axes[0, 1].set_ylabel('标准化残差', fontsize=11)\n",
    "axes[0, 1].set_title('标准化残差 vs 拟合值', fontsize=12, fontweight='bold')\n",
    "axes[0, 1].legend(fontsize=9)\n",
    "axes[0, 1].grid(alpha=0.3)\n",
    "\n",
    "# 3. 残差直方图\n",
    "axes[0, 2].hist(residuals, bins=20, color='coral', alpha=0.7, edgecolor='black', density=True)\n",
    "# 叠加正态分布曲线\n",
    "mu, std = residuals.mean(), residuals.std()\n",
    "x = np.linspace(residuals.min(), residuals.max(), 100)\n",
    "axes[0, 2].plot(x, stats.norm.pdf(x, mu, std), 'r-', linewidth=2, label='正态分布')\n",
    "axes[0, 2].set_xlabel('残差', fontsize=11)\n",
    "axes[0, 2].set_ylabel('密度', fontsize=11)\n",
    "axes[0, 2].set_title('残差分布直方图', fontsize=12, fontweight='bold')\n",
    "axes[0, 2].legend(fontsize=9)\n",
    "axes[0, 2].grid(axis='y', alpha=0.3)\n",
    "\n",
    "# 4. Q-Q图\n",
    "stats.probplot(residuals, dist=\"norm\", plot=axes[1, 0])\n",
    "axes[1, 0].set_title('残差Q-Q图', fontsize=12, fontweight='bold')\n",
    "axes[1, 0].grid(alpha=0.3)\n",
    "\n",
    "# 5. 残差序列图\n",
    "axes[1, 1].plot(range(len(residuals)), residuals, marker='o', linestyle='-', \n",
    "                color='purple', alpha=0.6, markersize=4)\n",
    "axes[1, 1].axhline(y=0, color='red', linestyle='--', linewidth=2)\n",
    "axes[1, 1].set_xlabel('样本序号', fontsize=11)\n",
    "axes[1, 1].set_ylabel('残差', fontsize=11)\n",
    "axes[1, 1].set_title('残差序列图', fontsize=12, fontweight='bold')\n",
    "axes[1, 1].grid(alpha=0.3)\n",
    "\n",
    "# 6. Scale-Location图(检验同方差性)\n",
    "sqrt_abs_resid = np.sqrt(np.abs(standardized_residuals))\n",
    "axes[1, 2].scatter(y_test_pred, sqrt_abs_resid, alpha=0.5, s=40, color='orange')\n",
    "# 添加平滑曲线\n",
    "from scipy.interpolate import make_interp_spline\n",
    "sorted_indices = np.argsort(y_test_pred)\n",
    "spl = make_interp_spline(y_test_pred[sorted_indices], sqrt_abs_resid[sorted_indices], k=3)\n",
    "x_smooth = np.linspace(y_test_pred.min(), y_test_pred.max(), 100)\n",
    "y_smooth = spl(x_smooth)\n",
    "axes[1, 2].plot(x_smooth, y_smooth, 'r-', linewidth=2)\n",
    "axes[1, 2].set_xlabel('拟合值', fontsize=11)\n",
    "axes[1, 2].set_ylabel('√|标准化残差|', fontsize=11)\n",
    "axes[1, 2].set_title('Scale-Location图', fontsize=12, fontweight='bold')\n",
    "axes[1, 2].grid(alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\n📊 诊断图解读:\")\n",
    "print(\"  1. 残差vs拟合值: 点应随机分布,无模式\")\n",
    "print(\"  2. 标准化残差vs拟合值: 95%的点应在±2σ内\")\n",
    "print(\"  3. 残差直方图: 应接近正态分布(钟形曲线)\")\n",
    "print(\"  4. Q-Q图: 点应在直线上\")\n",
    "print(\"  5. 残差序列图: 无趋势或周期\")\n",
    "print(\"  6. Scale-Location图: 红线应水平,检验同方差性\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 四、模型优化技巧\n",
    "\n",
    "### 4.1 特征工程\n",
    "- 多项式特征\n",
    "- 交互特征\n",
    "- 特征标准化\n",
    "\n",
    "### 4.2 正则化回归\n",
    "- Ridge回归(L2正则化)\n",
    "- Lasso回归(L1正则化)\n",
    "- ElasticNet回归"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 特征标准化对比\n",
    "print(\"=\"*100)\n",
    "print(\"🔧 特征标准化\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 标准化\n",
    "scaler = StandardScaler()\n",
    "X_train_scaled = scaler.fit_transform(X_train)\n",
    "X_test_scaled = scaler.transform(X_test)\n",
    "\n",
    "# 使用标准化数据训练模型\n",
    "model_scaled = LinearRegression()\n",
    "model_scaled.fit(X_train_scaled, y_train)\n",
    "\n",
    "# 预测和评估\n",
    "y_test_pred_scaled = model_scaled.predict(X_test_scaled)\n",
    "r2_scaled = r2_score(y_test, y_test_pred_scaled)\n",
    "\n",
    "print(\"\\n【标准化前后对比】\")\n",
    "print(\"=\"*80)\n",
    "print(f\"{'指标':<20} {'标准化前':<25} {'标准化后':<25}\")\n",
    "print(\"=\"*80)\n",
    "print(f\"{'测试集R²':<20} {r2_test:<25.4f} {r2_scaled:<25.4f}\")\n",
    "print(\"=\"*80)\n",
    "\n",
    "print(\"\\n💡 说明:\")\n",
    "print(\"  - 标准化使各特征处于相同量级,便于比较系数大小\")\n",
    "print(\"  - 对于线性回归,标准化不影响R²,但对正则化回归很重要\")\n",
    "print(\"  - 标准化后的系数可以直接比较,反映各特征的相对重要性\")\n",
    "\n",
    "# 对比系数\n",
    "print(\"\\n【系数对比】\")\n",
    "coef_comparison = pd.DataFrame({\n",
    "    '特征': feature_names,\n",
    "    '原始系数': coefs_mlr,\n",
    "    '标准化后系数': model_scaled.coef_\n",
    "})\n",
    "print(coef_comparison.to_string(index=False))\n",
    "print(\"\\n  标准化后系数的绝对值大小可直接反映特征重要性\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 五、课程总结\n",
    "\n",
    "### 核心知识点\n",
    "\n",
    "1. **线性回归基础**\n",
    "   - 一元线性回归: Y = β₀ + β₁X + ε\n",
    "   - 多元线性回归: Y = β₀ + β₁X₁ + ... + βₙXₙ + ε\n",
    "   - 最小二乘法(OLS)求解\n",
    "\n",
    "2. **模型评估指标**\n",
    "   - R²(决定系数): 衡量拟合优度,越接近1越好\n",
    "   - RMSE(均方根误差): 预测误差,越小越好\n",
    "   - MAE(平均绝对误差): 平均偏差,越小越好\n",
    "\n",
    "3. **模型诊断**\n",
    "   - 残差分析(四图诊断)\n",
    "   - 线性回归五大假设检验\n",
    "   - 多重共线性检验(VIF)\n",
    "\n",
    "4. **Python实现**\n",
    "   ```python\n",
    "   # sklearn: 快速建模和预测\n",
    "   from sklearn.linear_model import LinearRegression\n",
    "   model = LinearRegression()\n",
    "   model.fit(X_train, y_train)\n",
    "   y_pred = model.predict(X_test)\n",
    "   \n",
    "   # statsmodels: 详细统计检验\n",
    "   import statsmodels.api as sm\n",
    "   model = sm.OLS(y, X)\n",
    "   results = model.fit()\n",
    "   print(results.summary())\n",
    "   ```\n",
    "\n",
    "5. **业务应用**\n",
    "   - 销售预测\n",
    "   - 价格预测\n",
    "   - 因素影响分析\n",
    "   - ROI评估\n",
    "\n",
    "### 线性回归优缺点\n",
    "\n",
    "**优点**:\n",
    "- ✅ 模型简单,易于理解和解释\n",
    "- ✅ 计算效率高,适合大数据\n",
    "- ✅ 系数有明确业务含义\n",
    "- ✅ 可以进行统计推断(显著性检验)\n",
    "\n",
    "**缺点**:\n",
    "- ❌ 只能建模线性关系\n",
    "- ❌ 对异常值敏感\n",
    "- ❌ 需要满足多个假设\n",
    "- ❌ 存在多重共线性时效果差\n",
    "\n",
    "### 使用建议\n",
    "\n",
    "1. **何时使用线性回归**:\n",
    "   - 变量间存在线性关系\n",
    "   - 需要解释性强的模型\n",
    "   - 数据满足基本假设\n",
    "\n",
    "2. **何时不用线性回归**:\n",
    "   - 关系明显非线性(考虑多项式回归或其他模型)\n",
    "   - 因变量是分类变量(使用逻辑回归)\n",
    "   - 数据严重违反假设\n",
    "\n",
    "3. **提升模型效果**:\n",
    "   - 特征工程(交互特征、多项式特征)\n",
    "   - 异常值处理\n",
    "   - 特征标准化\n",
    "   - 正则化(Ridge、Lasso)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 六、课后作业\n",
    "\n",
    "### 作业1:广告效果分析(基础)\n",
    "\n",
    "某公司在电视、广播、报纸三种媒体投放广告,想分析各媒体对销售额的影响。\n",
    "\n",
    "要求:\n",
    "1. 建立多元线性回归模型\n",
    "2. 分析各媒体的影响系数\n",
    "3. 计算R²和RMSE\n",
    "4. 进行残差分析\n",
    "5. 预测:电视20万、广播10万、报纸5万的销售额"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 作业1数据生成\n",
    "np.random.seed(42)\n",
    "n = 100\n",
    "\n",
    "homework1_data = pd.DataFrame({\n",
    "    '电视广告': np.random.uniform(5, 50, n),\n",
    "    '广播广告': np.random.uniform(2, 30, n),\n",
    "    '报纸广告': np.random.uniform(1, 20, n)\n",
    "})\n",
    "\n",
    "# 销售额 = 50 + 3*电视 + 2*广播 + 0.5*报纸 + 噪声\n",
    "homework1_data['销售额'] = (50 + 3 * homework1_data['电视广告'] + \n",
    "                           2 * homework1_data['广播广告'] + \n",
    "                           0.5 * homework1_data['报纸广告'] + \n",
    "                           np.random.normal(0, 10, n)).round(2)\n",
    "\n",
    "homework1_data['电视广告'] = homework1_data['电视广告'].round(2)\n",
    "homework1_data['广播广告'] = homework1_data['广播广告'].round(2)\n",
    "homework1_data['报纸广告'] = homework1_data['报纸广告'].round(2)\n",
    "\n",
    "print(\"作业1数据预览:\")\n",
    "print(homework1_data.head())\n",
    "\n",
    "# TODO: 在此完成作业1\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 作业2:员工薪资预测(进阶)\n",
    "\n",
    "某公司想建立薪资预测模型,数据包含工作年限、学历、绩效评分、加班时长等。\n",
    "\n",
    "要求:\n",
    "1. 划分训练集和测试集(80/20)\n",
    "2. 建立多元线性回归模型\n",
    "3. 在训练集和测试集上评估模型\n",
    "4. 进行完整的模型诊断(五大假设)\n",
    "5. 使用statsmodels查看各系数的显著性\n",
    "6. 计算VIF,检查多重共线性\n",
    "7. 预测新员工的薪资"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 作业2数据生成\n",
    "np.random.seed(42)\n",
    "n = 300\n",
    "\n",
    "homework2_data = pd.DataFrame({\n",
    "    '工作年限': np.random.poisson(5, n).clip(0, 20),\n",
    "    '学历': np.random.choice([1, 2, 3], n, p=[0.5, 0.35, 0.15]),  # 1=本科, 2=硕士, 3=博士\n",
    "    '绩效评分': np.random.choice([60, 70, 80, 90, 100], n, p=[0.05, 0.15, 0.4, 0.3, 0.1]),\n",
    "    '加班时长': np.random.uniform(0, 50, n),\n",
    "    '培训次数': np.random.poisson(3, n)\n",
    "})\n",
    "\n",
    "# 月薪 = 5000 + 500*年限 + 2000*学历 + 30*绩效 + 20*加班 + 100*培训 + 噪声\n",
    "homework2_data['月薪'] = (5000 + 500 * homework2_data['工作年限'] + \n",
    "                         2000 * homework2_data['学历'] + \n",
    "                         30 * homework2_data['绩效评分'] + \n",
    "                         20 * homework2_data['加班时长'] + \n",
    "                         100 * homework2_data['培训次数'] + \n",
    "                         np.random.normal(0, 1000, n)).round(0)\n",
    "\n",
    "homework2_data['加班时长'] = homework2_data['加班时长'].round(1)\n",
    "\n",
    "print(\"作业2数据预览:\")\n",
    "print(homework2_data.head())\n",
    "\n",
    "# TODO: 在此完成作业2\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 作业3:二手车价格预测(综合)\n",
    "\n",
    "建立二手车价格预测模型,数据包含品牌、车龄、里程、排量等。\n",
    "\n",
    "要求:\n",
    "1. 完整的EDA(相关性分析、散点图矩阵)\n",
    "2. 数据预处理(处理分类变量、异常值)\n",
    "3. 特征工程(考虑交互特征或多项式特征)\n",
    "4. 建立并评估模型\n",
    "5. 完整的模型诊断\n",
    "6. 使用标准化后的数据重新训练\n",
    "7. 对比分析不同模型的效果\n",
    "8. 撰写完整的分析报告"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 作业3数据生成\n",
    "np.random.seed(42)\n",
    "n = 500\n",
    "\n",
    "homework3_data = pd.DataFrame({\n",
    "    '品牌': np.random.choice(['大众', '丰田', '本田', '福特', '日产'], n),\n",
    "    '车龄': np.random.uniform(1, 10, n),\n",
    "    '里程': np.random.uniform(1, 15, n),  # 万公里\n",
    "    '排量': np.random.choice([1.5, 1.8, 2.0, 2.5, 3.0], n, p=[0.3, 0.25, 0.25, 0.15, 0.05]),\n",
    "    '变速箱': np.random.choice(['手动', '自动'], n, p=[0.3, 0.7]),\n",
    "    '事故次数': np.random.choice([0, 1, 2], n, p=[0.7, 0.25, 0.05])\n",
    "})\n",
    "\n",
    "# 价格(万元) = 基础价 - 车龄影响 - 里程影响 + 排量影响 - 事故影响 + 变速箱影响 + 噪声\n",
    "brand_base = {'大众': 15, '丰田': 18, '本田': 16, '福特': 14, '日产': 15}\n",
    "homework3_data['基础价'] = homework3_data['品牌'].map(brand_base)\n",
    "transmission_bonus = homework3_data['变速箱'].map({'手动': 0, '自动': 2})\n",
    "\n",
    "homework3_data['价格'] = (homework3_data['基础价'] - \n",
    "                         1.5 * homework3_data['车龄'] - \n",
    "                         0.3 * homework3_data['里程'] + \n",
    "                         2 * homework3_data['排量'] - \n",
    "                         2 * homework3_data['事故次数'] + \n",
    "                         transmission_bonus + \n",
    "                         np.random.normal(0, 2, n)).clip(3, 30)\n",
    "\n",
    "homework3_data = homework3_data.drop('基础价', axis=1)\n",
    "homework3_data['车龄'] = homework3_data['车龄'].round(1)\n",
    "homework3_data['里程'] = homework3_data['里程'].round(1)\n",
    "homework3_data['价格'] = homework3_data['价格'].round(2)\n",
    "\n",
    "print(\"作业3数据预览:\")\n",
    "print(homework3_data.head(10))\n",
    "\n",
    "# TODO: 在此完成作业3\n",
    "# 提示:\n",
    "# 1. 分类变量需要进行编码(pd.get_dummies()或LabelEncoder)\n",
    "# 2. 可以尝试车龄×里程的交互特征\n",
    "# 3. 对比原始数据和标准化数据的模型效果\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 课程结束\n",
    "\n",
    "**下一讲预告**: 第4阶段_第6讲_综合分析实战\n",
    "\n",
    "将学习:\n",
    "- 完整的数据分析项目流程\n",
    "- 综合运用所有分析方法\n",
    "- 真实业务场景案例\n",
    "- 分析报告撰写\n",
    "\n",
    "---\n",
    "\n",
    "📧 如有疑问,请联系助教\n",
    "\n",
    "✅ 完成作业后,请提交Jupyter Notebook文件"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
